# gen.rb # usage: ruby gen.rb schema.sql packageName # generates EJB CMP entity beans and XML deployment descriptors from SQL Schema # places java files in out/packageName # places XML files in out/META-INF # Programming by Eric Rollins # # Copyright (c) 2002 Eric Rollins # www.acm.org/~rollins # gen comes with Absolutely No Warranty. # This is free software, and you are welcome to # redistribute it under certain conditions # (for details see:GNU General Public License, # http://www.gnu.org/copyleft/gpl.html) # version 0.1 #============================================================================ def to_java_name(sql_name) upNext = TRUE retString = "" sql_name = sql_name.sub(/ejb_/,"") sql_name.each_byte { |c| if c == '_'[0] upNext = TRUE else tmp = "" << c if upNext tmp.upcase! upNext = FALSE end retString << tmp end } return retString end #============================================================================ def downfirst(str) first = "" << str[0] rest = str[1..str.length] return first.downcase + rest end #============================================================================ class Column # Table object this column belongs to attr_reader :table # String name of Column in database attr_reader :name # String name of type from database (currently "int", "varchar") attr_reader :sql_type attr_writer :sql_type # is column unique (TRUE or FALSE) attr_reader :unique attr_writer :unique # is column primary key (TRUE or FALSE) attr_reader :primary_key attr_writer :primary_key # is column not null (TRUE or FALSE) attr_reader :not_null attr_writer :not_null # Column object this column as a foreign key points to, or nil attr_reader :references attr_writer :references # String java translation of column name attr_reader :java_name attr_writer :java_name # String java type for this column (currently "int", "String", "Integer") attr_reader :java_type attr_writer :java_type def initialize(table,name) @table = table @name = name @java_name = to_java_name(name) end def print if references reftable = references.table.name refcolumn = references.name end Kernel.print " ",@name, " java_name=", @java_name, " sql_type=",@sql_type, " unique=", @unique, " primary_key=", @primary_key, " not_null=", @not_null, " references=", reftable, "(",refcolumn, ")\n" end end #============================================================================ class Table # String name of Table in database attr_reader :name # Hash of Column objects belonging to this Table attr_reader :columns # Hash of Column objects belonging to other Tables which point to this Table attr_reader :referred_by # Array of Column objects belonging to this Table # (used to maintain columns in order from database) attr_reader :columnsArray # String java translation of Table name attr_reader :java_name def initialize(name) @name = name @columns = {} @referred_by = {} @columnsArray = [] @java_name = to_java_name(name) end def addColumn(column) @columns[column.name] = column @columnsArray.push(column) end def print Kernel.print "\n================================\n" Kernel.print @name, "(",@java_name,")\n" @columnsArray.each {|col| col.print } Kernel.print "referred_by:\n" @referred_by.each {|rbn, rb| Kernel.print " ",rb.table.name,"(",rb.name,")\n" } end end #============================================================================ def write_entity(table) className = table.java_name + "Entity" fileName = JAVA_DIR + className + ".java" f = File.new(fileName,"w"); f.print "// ", className,".java\n\n" f.print "package ", $packageName,";\n\n" f.print "import javax.ejb.EJBLocalObject;\n" f.print "import javax.ejb.EJBException;\n\n" f.print "public interface ", className, " extends EJBLocalObject {\n" table.columnsArray.each {|column| f.print " public ", column.java_type, " get", column.java_name, "() throws EJBException;\n" f.print " public void set", column.java_name, "(", column.java_type, " ", downfirst(column.java_name), ") throws EJBException;\n" } f.print("}\n") f.close() end #============================================================================ def write_entity_home(table) className = table.java_name + "Entity" homeName = className + "Home" fileName = JAVA_DIR + homeName + ".java" f = File.new(fileName,"w"); f.print "// ", homeName,".java\n\n" f.print "package ", $packageName,";\n\n" f.print "import javax.ejb.EJBLocalHome;\n" f.print "import javax.ejb.EJBException;\n" f.print "import java.io.Serializable;\n" f.print "import javax.ejb.CreateException;\n" f.print "import javax.ejb.FinderException;\n\n" f.print "public interface ", homeName, " extends EJBLocalHome {\n" f.print " public ", className, " create(" first = TRUE table.columnsArray.each {|column| # next if column.references next if column.primary_key if first first = FALSE else f.print ", " end f.print column.java_type, " ", downfirst(column.java_name) } f.print ")\n throws EJBException, CreateException;\n" f.print " public ", className, " findByPrimaryKey(Integer id)\n" f.print " throws FinderException, EJBException;\n" f.print " public java.util.Collection findAll()\n" f.print " throws FinderException, EJBException;\n" f.print("}\n") f.close() end #============================================================================ def write_entity_bean(table) className = table.java_name + "EntityBean" fileName = JAVA_DIR + className + ".java" f = File.new(fileName,"w"); f.print "// ", className,".java\n\n" f.print "package ", $packageName,";\n\n" f.print "import javax.ejb.EntityBean;\n" f.print "import javax.ejb.EntityContext;\n" f.print "import dwarf.Sequence;\n\n" f.print "public abstract class ", className, " implements EntityBean {\n" f.print " transient private EntityContext ctx;\n\n" # ----------- # ejbCreate f.print " public Integer ejbCreate(" first = TRUE table.columnsArray.each {|column| # next if column.references next if column.primary_key if first first = FALSE else f.print ", " end f.print column.java_type, " ", downfirst(column.java_name) } f.print "){\n" table.columnsArray.each {|column| next if column.references f.print " set",column.java_name,"(" if column.primary_key f.print "new Integer(Sequence.next(\"",table.name,"_",column.name, "_seq\")));\n" else f.print downfirst(column.java_name),");\n" end } f.print " return null;\n" f.print " }\n\n" # ----------- # ejbPostCreate f.print " public void ejbPostCreate(" first = TRUE table.columnsArray.each {|column| # next if column.references next if column.primary_key if first first = FALSE else f.print ", " end f.print column.java_type, " ", downfirst(column.java_name) } f.print "){\n" table.columnsArray.each {|column| next if !column.references f.print " set",column.java_name,"(" f.print downfirst(column.java_name),");\n" } f.print " }\n\n" #---------- table.columnsArray.each {|column| f.print " public abstract ", column.java_type, " get", column.java_name, "();\n" f.print " public abstract void set", column.java_name, "(", column.java_type, " ", downfirst(column.java_name), ");\n" } f.print " public void ejbRemove() {}\n" f.print " public void ejbActivate() {}\n" f.print " public void ejbPassivate() {}\n" f.print " public void setEntityContext(EntityContext ctx) { this.ctx = ctx; }\n" f.print " public void unsetEntityContext() { this.ctx = null; }\n" f.print " public void ejbLoad() {}\n" f.print " public void ejbStore() {}\n" f.print "}\n" f.close() end #============================================================================ def write_xml_jar(tables) f = File.new(XML_DIR + "ejb-jar.xml.frag", "w") f.print " \n" tables.each { |tablename, table| f.print " \n" f.print " ",table.java_name,"EntityEJB\n" f.print " ",$packageName,".",table.java_name, "Entity\n" f.print " ",$packageName,".",table.java_name, "EntityHome\n" f.print " ",$packageName,".",table.java_name, "EntityBean\n" f.print " Container\n" f.print " java.lang.Integer\n" f.print " False\n" f.print " 2.x\n" f.print " ",table.java_name, "\n" table.columnsArray.each {|column| next if column.references f.print " ", downfirst(column.java_name), "\n" } table.columnsArray.each {|column| next if !column.primary_key f.print " ", downfirst(column.java_name), "\n" } table.columnsArray.each {|column| next if !column.references f.print " \n" f.print " ",$packageName,"/", column.references.table.java_name, "Entity\n" f.print " Entity\n" f.print " ",$packageName,".", column.references.table.java_name, "EntityHome\n" f.print " ",$packageName,".", column.references.table.java_name,"Entity\n" f.print " ",column.references.table.java_name, "EntityEJB\n" f.print " \n" } f.print " \n" f.print " \n" f.print " findAll\n" f.print " \n" f.print " \n" f.print " \n" f.print " SELECT OBJECT(o)\n" f.print " FROM ", table.java_name, " o\n" f.print " \n" f.print " \n" f.print " \n" } f.print " \n" f.print " \n" tables.each { |tablename, table| table.columnsArray.each {|column| next if !column.references f.print " \n" f.print " ",column.java_name, "\n" f.print " \n" f.print " ", downfirst(table.java_name),"-has-", downfirst(column.java_name), "\n" f.print " Many\n" f.print " \n" f.print " ",table.java_name, "EntityEJB\n" f.print " \n" f.print " \n" f.print " ",downfirst(column.java_name), "\n" f.print " \n" f.print " \n" f.print " \n" f.print " ", downfirst(column.java_name),"-of-", downfirst(table.java_name), "\n" f.print " One\n" f.print " \n" f.print " ",column.references.table.java_name, "EntityEJB\n" f.print " \n" f.print " \n" f.print " \n" } } f.print " \n" f.print " \n" tables.each { |tablename, table| f.print " \n" f.print " \n" f.print " ",table.java_name, "EntityEJB\n" f.print " *\n" f.print " \n" f.print " Required\n" f.print " \n" } f.print " \n" f.close() end #============================================================================ def write_jboss_xml(tables) f = File.new(XML_DIR + "jboss.xml.frag", "w") f.print " \n" tables.each { |tablename, table| f.print " \n" f.print " ", table.java_name,"EntityEJB\n" f.print " ",$packageName,"/",table.java_name, "Entity\n" f.print " \n" } f.print " \n" f.close() end #============================================================================ def write_jbosscmp_jdbc_xml(tables) f = File.new(XML_DIR + "jbosscmp-jdbc.xml.frag", "w") f.print " \n" tables.each { |tablename, table| f.print " \n" f.print " ", table.java_name,"EntityEJB\n" f.print " ", table.name, "\n" table.columnsArray.each {|column| next if column.references f.print " \n" f.print " ", downfirst(column.java_name), "\n" f.print " ", column.name, "\n" f.print " \n" } f.print " \n" } f.print " \n" f.print " \n" tables.each { |tablename, table| table.columnsArray.each {|column| next if !column.references f.print " \n" f.print " ",column.java_name, "\n" f.print " \n" f.print " \n" f.print " ", downfirst(table.java_name),"-has-", downfirst(column.java_name), "\n" f.print " \n" f.print " \n" f.print " \n" f.print " ", downfirst(column.java_name),"-of-", downfirst(table.java_name), "\n" f.print " \n" f.print " \n" f.print " ", downfirst(column.references.java_name), "\n" f.print " ",column.name,"\n" f.print " \n" f.print " \n" f.print " \n" f.print " \n" } } f.print " \n" f.close() end #============================================================================ def process_column(line,column) if line =~ /unique/i column.unique = TRUE end if line =~/primary key/i column.primary_key = TRUE end if line =~ /not null/i column.not_null = TRUE end if column.sql_type == "int" if column.primary_key column.java_type = "Integer" else column.java_type = "int" end elsif column.sql_type == "varchar" column.java_type = "String" end end #============================================================================ # constants REX_CREATE_TABLE = /create table ([\w|_]+)/i REX_INT_COLUMN = /([\w|_]+)\s+int/i REX_VARCHAR_COLUMN = /([\w|_]+)\s+varchar/i REX_ALTER_TABLE = /alter table ([\w|_]+)/i REX_CONSTRAINT = /constraint ([\w|_]+)\s+foreign key\s*\(([\w|_]+)/i REX_REFERENCES = /references ([\w|_]+)\s*\(([\w|_]+)/i REX_PRIMARY_KEY = /primary key \(([\w|_|,|\s]+)\)/i tables = {} current_table = nil current_alter_table = nil current_fk_column = nil if ARGV.length != 2 print "usage: ruby gen.rb schema.sql packageName\n" exit end schema = File.open(ARGV[0],"r") $packageName = ARGV[1] JAVA_DIR = "out/" + $packageName + "/" XML_DIR = "out/META-INF/" schema.each_line {|line| if line =~ REX_CREATE_TABLE current_table = Table.new($1) tables[$1] = current_table elsif line =~ REX_INT_COLUMN column = Column.new(current_table,$1) column.sql_type = "int" process_column(line,column) current_table.addColumn(column) elsif line =~ REX_VARCHAR_COLUMN column = Column.new(current_table,$1) column.sql_type = "varchar" process_column(line,column) current_table.addColumn(column) elsif line =~ REX_ALTER_TABLE current_alter_table = tables[$1] elsif line =~ REX_CONSTRAINT current_fk_column = current_alter_table.columns[$2] elsif line =~ REX_REFERENCES current_fk_column.references = tables[$1].columns[$2] fk_tname = $1 current_fk_column.java_name = current_fk_column.java_name.sub(/id/i,"") current_fk_column.java_type = tables[fk_tname].java_name + "Entity" tables[fk_tname].referred_by[current_alter_table.name] = current_fk_column elsif line =~ REX_PRIMARY_KEY keys = $1.split(",") keys.each { |key| current_table.columns[key].primary_key = TRUE } end } schema.close tables.each { |tablename, table| table.print } tables.each { |tablename, table| write_entity(table) } tables.each { |tablename, table| write_entity_home(table) } tables.each { |tablename, table| write_entity_bean(table) } write_xml_jar(tables) write_jboss_xml(tables) write_jbosscmp_jdbc_xml(tables)